package com.zoro.barn.cloud.gateway.security;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.zoro.barn.cloud.gateway.loadbalance.WebClientComponent;
import com.zoro.barn.commons.foundation.constants.web.RequestConstants;
import com.zoro.barn.commons.foundation.constants.web.ResponseConstants;
import com.zoro.barn.commons.tools.encoding.Base64Util;
import com.zoro.barn.commons.tools.json.JsonUtil;
import com.zoro.barn.commons.tools.string.StringTools;
import com.zoro.barn.dto.login.PasswordUserDto;
import com.zoro.barn.dto.login.TokenDto;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 用于对请求做登陆认证
 *
 * @author zhaoxingwu
 */
public class AuthenticationGatewayFilter implements GatewayFilter {

    private static final String AUTHORIZATION_HEADER = "Authorization";
    private final Logger logger = LoggerFactory.getLogger(getClass());

    private final Set<Pattern> ignoreUriPattern;
    private final Set<String> ignoreUri;

    private final String checkTokenUrl;
    private final WebClientComponent webClientComponent;

    public AuthenticationGatewayFilter(Collection<String> ignoreUriPattern, Collection<String> ignoreUri, WebClientComponent webClientComponent, String checkTokenUrl) {
        if (CollectionUtils.isEmpty(ignoreUriPattern)) {
            this.ignoreUriPattern = new HashSet<>();
        } else {
            this.ignoreUriPattern = ignoreUriPattern.stream().map(Pattern::compile).collect(Collectors.toSet());
        }
        this.ignoreUri = new HashSet<>(ignoreUri);
        this.webClientComponent = webClientComponent;
        this.checkTokenUrl = "http://" + StringTools.trim(checkTokenUrl, "/");
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String uri = exchange.getRequest().getURI().getPath();
        if (this.isIgnoreUri(uri)) {
            return chain.filter(exchange);
        }
        if (this.isIgnoreUriPattern(uri)) {
            return chain.filter(exchange);
        }

        // 验证身份
        ServerHttpRequest request = exchange.getRequest();
        List<String> headers = request.getHeaders().get(AUTHORIZATION_HEADER);
        String token = CollectionUtils.isEmpty(headers) ? null : headers.get(0);
        if (StringUtils.isBlank(token)) {
            // 没有token 视为没有登陆
            ServerHttpResponse response = exchange.getResponse();
            return this.writeUnauthorizedResponseBody(response);
        } else {
            // 去验证token ,可能过期，可能不合法
            Mono<PasswordUserDto> authorized = isAuthorized(token);
            return authorized
                    .doOnError(e -> logger.error("error occurred when check token.", e))
                    .onErrorReturn(new PasswordUserDto())
                    .flatMap(v -> {
                        if (v != null && StringUtils.isNotBlank(v.getAccount())) {

                            // 增加请求头，好让下游服务可以在请求头中识别身份
                            ServerHttpRequest req = request.mutate()
                                    .header(RequestConstants.USER_NAME_HEADER, Base64Util.urlEncoding(v.getUserName()))
                                    .header(RequestConstants.USER_ACCOUNT_HEADER, Base64Util.urlEncoding(v.getAccount()))
                                    .header(RequestConstants.USER_PID_HEADER, v.getPid())
                                    .build();

                            return chain.filter(exchange.mutate().request(req).build());
                        } else {
                            return this.writeUnauthorizedResponseBody(exchange.getResponse());
                        }
                    });
        }

    }

    /**
     * token 是否合法
     * @param token token
     * @return true or false
     */
    private Mono<PasswordUserDto> isAuthorized(String token) {
        TokenDto tokenEntity = new TokenDto();
        tokenEntity.setToken(token);

        return this.webClientComponent
                .postUnwrap(checkTokenUrl, tokenEntity, PasswordUserDto.class);
    }

    /**
     * 向前台写入没有登陆的响应值
     * @param response response
     * @return void
     */
    private Mono<Void> writeUnauthorizedResponseBody(ServerHttpResponse response) {
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        Map<String, String> data = new HashMap<>();
        data.put(ResponseConstants.STATUS, ResponseConstants.STATUS_ERROR);
        data.put(ResponseConstants.MESSAGE, "not login");
        try {
            return response.writeWith(Flux.just(response.bufferFactory().wrap(JsonUtil.toBytes(data))));
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 判定 {@param uri} 是不是可以被忽略, 使用字符串匹配
     *
     * @param uri uri
     * @return yes or no
     */
    private boolean isIgnoreUri(String uri) {
        return this.ignoreUri.contains(uri);
    }

    /**
     * 判定 {@param uri} 是不是可以被忽略, 使用正则匹配
     *
     * @param uri uri
     * @return yes or no
     */
    private boolean isIgnoreUriPattern(String uri) {
        for (Pattern pattern : this.ignoreUriPattern) {
            if (pattern.matcher(uri).find()) {
                return true;
            }
        }
        return false;
    }


}
